<template>
  <div ref="canvas"></div>
</template>

<script lang="ts" setup>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"
import { ref, onMounted } from "vue";

// 网格模型Mesh其实就一个一个三角形(面)拼接构成。
// 使用网格模型Mesh渲染几何体geometry，就是几何体所有顶点坐标三个为一组，构成一个三角形，
// 多组顶点构成多个三角形，就可以用来模拟表示物体的表面。
// 矩形平面几何体至少需要两个三角形拼接而成:
//   const geometry = new THREE.PlaneGeometry(100, 50, 1, 1); 第3个参数表示宽的细分数，第4个参数表示高的细分数
//   那么这个矩形平面有几个三角形呢？宽高都是1，那么就只有一个矩形，但是一个矩形是由两个三角形组成，则 三角形数量为 1 x 2
//   同理这个矩形平面 const geometry = new THREE.PlaneGeometry(100,50,2,2);
//   则有 2(宽的细分数) x 2(高的细分数) x 2(每个矩形两个三角形) 个矩形
// 而对于一个曲面而言，细分数越大，表面越光滑，同时是三角形和顶点数量就会越多
// 几何体三角形数量或者说顶点数量直接影响Three.js的渲染性能，在不影响渲染效果的情况下，一般尽量越少越好

// 画布
const canvas = ref<any>('');
// 场景
const scene = new THREE.Scene();
// 自定义几何体
const customGeometry = new THREE.BufferGeometry();
// 通过javascript类型化数组 Float32Array 创建一组xyz坐标数据用来表示几何体的顶点坐标
const vertices = new Float32Array([
  0, 0, 0, // 顶点1坐标
  50, 0, 0, // 顶点2坐标
  0, 100, 0, // 顶点3坐标

  0, 0, 0, // 顶点4坐标
  0, 0, 100, // 顶点5坐标
  50, 0, 0, // 顶点6坐标

  0, 100, 0, // 顶点7坐标 与 顶点3坐标 重合
  0, 0, 100, // 顶点8坐标 与 顶点5坐标 重合
  50, 0, 0, // 顶点9坐标 与 顶点6坐标 重合

  0, 100, 0, // 顶点10坐标 与 顶点3，顶点7 坐标 重合
  0, 0, 0, // 顶点11坐标 与 顶点4，顶点1 坐标 重合
  0, 0, 100, // 顶点12坐标 与 顶点5，顶点8 坐标 重合
]);
// 创建属性缓冲区对象
// 3个为一组，表示一个顶点的xyz坐标
const attribute = new THREE.BufferAttribute(vertices, 3);
// 设置几何体attributes属性的位置属性
customGeometry.attributes.position = attribute;

// 点模型材质
const pointMaterial = new THREE.PointsMaterial({
  color: 0xffff00,
  size: 10.0, //点对象像素尺寸
  side: THREE.DoubleSide, // 双面都展示
});
// 点模型Mesh
const pointMesh = new THREE.Points(customGeometry, pointMaterial);
scene.add(pointMesh);

// 线模型材质
const lineMaterial = new THREE.LineBasicMaterial({
  color: 0xff0000, //线条颜色
  side: THREE.DoubleSide, // 双面都展示
});
// 线模型Mesh
const line = new THREE.Line(customGeometry, lineMaterial);
scene.add(line);
// 闭合线条
// const line = new THREE.LineLoop(customGeometry, lineMaterial);
// scene.add(line);
//非连续的线条
// const line = new THREE.LineSegments(customGeometry, lineMaterial);
// scene.add(line);

// 网格模型 - 材质 (三个顶点成一面) 但是这里不能使用受光照影响的材质(Lambert, Phong, Standard, Physical)，会导致无法渲染
// 使用受光照影响的材质，几何体BufferGeometry需要定义顶点法线数据

// const material = new THREE.MeshBasicMaterial({
//   color: 0x0000ff, // 设置材质颜色
//   side: THREE.DoubleSide, // 双面都展示
// });

const material = new THREE.MeshLambertMaterial({
  color: 0x0000ff, // 设置材质颜色,
  side: THREE.DoubleSide, // 双面都展示
});
// 法线
const normalVertices = new Float32Array([
  0, 0, 1, // 顶点1法线坐标
  0, 0, 1, // 顶点2法线坐标
  0, 0, 1, // 顶点3法线坐标

  0, 0, 1, // 顶点4法线坐标
  0, 0, 1, // 顶点5法线坐标
  0, 0, 1, // 顶点6法线坐标

  0, 0, 1, // 顶点7法线坐标
  0, 0, 1, // 顶点8法线坐标
  0, 0, 1, // 顶点9法线坐标

  0, 0, 1, // 顶点10法线坐标
  0, 0, 1, // 顶点11法线坐标
  0, 0, 1, // 顶点12法线坐标
]);
customGeometry.attributes.normal = new THREE.BufferAttribute(normalVertices, 3);

// 网格模型Mesh
const mesh = new THREE.Mesh(customGeometry, material);
scene.add(mesh);

// 光源 - 环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
// 相机
const camera = new THREE.PerspectiveCamera(100, window.innerWidth / window.innerHeight, 1, 4000);
camera.position.set(200, 200, 200);
camera.lookAt(0,0,0);
// 渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
// 渲染函数
function render() {
  renderer.render(scene, camera);
  requestAnimationFrame(render);
}
// 设置相机控件轨道控制器OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);
// 相机控件.target属性在OrbitControls.js内部表示相机目标观察点，默认0,0,0
controls.update();

onMounted(() => {
  canvas.value.appendChild(renderer.domElement);
  render();
});

</script>
